package com.wedimob.slideshare.video;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Map;
import java.util.Observable;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.protocol.HttpContext;

import com.wedimob.slideshare.RuntimeConfig;
import com.wedimob.slideshare.task.disk.DefaultWriter;
import com.wedimob.slideshare.task.disk.PathHelper;

public abstract class JavaVideoDownloader extends Observable {

    private static final char[] ILLEGAL_FILENAME_CHARACTERS = { '/', '\n',
            '\r', '\t', '\0', '\f', '`', '?', '*', '\\', '<', '>', '|', '\"',
            ':' };

    abstract protected Map<String, String> getUrls();

    protected String outputDir;

    public File getFile(String format) {
        try {
            // force charging urls
            if (info == null)
                getUrls();
            String filename = cleanFilename(info.title);
            if (filename == null || filename.length() == 0) {
                filename = info.videoId;
            }
            filename += "." + getExtension(format);
            filename = filename.replace(" ", "%20");
            File outputfile = null;
            if(outputDir.startsWith("file:/")) {                
                outputfile = new File((outputDir + "/"+   filename).replace(PathHelper.buildTmpPath().toString(), RuntimeConfig.getInstance().getTmpdir()));
            }
            else
                outputfile = new File(outputDir,  filename);
            return outputfile;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public String getBestFormat(VideoType type, VideoFormat size) {
        // force charging urls
        if (info == null)
            getUrls();

        if (info.sizes == null)
            System.err.println("info.sizes == null for http://www.youtube.com/watch?v=" + info.videoId);

        for (String format : info.sizes.keySet()) {
            if (format.contains(type.toString()) && info.sizes.get(format) > 0) {
                if (format.contains(size.toString())) {
                    return format;
                }
                if ("small".equals(size.toString())) {
                    if (format.contains("240") || format.contains("360")) {
                        return format;
                    }
                }
                if ("medium".equals(size.toString())) {
                    if (format.contains("480")) {
                        return format;
                    }
                }
                if ("large".equals(size.toString())) {
                    if (format.contains("720") || format.contains("1080")) {
                        return format;
                    }
                }
            }
        }
        // in extreme cases assimil medium with small
        for (String format : info.sizes.keySet()) {
            if (format.contains(type.toString()) && info.sizes.get(format) > 0) {
                if ("medium".equals(size.toString()) || "small".equals(size.toString())) {
                    if (format.contains("480") || format.contains("240") || format.contains("360")
                            || format.contains("medium") || format.contains("small")) {
                        return format;
                    }
                }
            }
        }
        System.err.println("no format for " + type + " " + size);
        return null;
    }

    protected VideoInfo info;
    private Integer progress;

    public Integer getProgress() {
        return progress;
    }

    protected static class VideoInfo {
        public String videoId;
        public String title;
        public Map<String, String> urls;
        public Map<String, Integer> sizes;
        public HttpClient httpclient;
        public HttpContext localContext;
        public String userAgent;
        public String encoding;
    }

    protected static String getExtension(String format) {
        if (format.contains("mp4"))
            return "mp4";

        if (format.contains("flv"))
            return "flv";

        if (format.contains("webm"))
            return "webm";

        if (format.contains("3gpp"))
            return "3gpp";

        return "mp3";
    }

    protected Integer getFileSize(String urli) {
        try {
            URL url = new URL(urli);
            URLConnection connection = url.openConnection();
            connection.connect();
            return connection.getContentLength();
        } catch (Exception e) {
            return 0;
        }
    }

    protected void setProgress(Integer progress) {
        if (progress != this.progress) {
            setChanged();
            notifyObservers(progress);
        }

        this.progress = progress;
    }

    protected static String humanReadableByteCount(long bytes, boolean si) {
        int unit = si ? 1000 : 1024;
        if (bytes < unit)
            return bytes + " B";
        int exp = (int) (Math.log(bytes) / Math.log(unit));
        String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i");
        return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
    }

    protected String cleanFilename(String filename) {
        for (char c : ILLEGAL_FILENAME_CHARACTERS) {
            filename = filename.replace(c, '_');
        }
        return filename;
    }

    protected String getStringFromInputStream(String encoding,
            InputStream instream) throws UnsupportedEncodingException,
            IOException {
        Writer writer = new StringWriter();

        char[] buffer = new char[1024];
        try {
            Reader reader = new BufferedReader(new InputStreamReader(instream,
                    encoding));
            int n;
            while ((n = reader.read(buffer)) != -1) {
                writer.write(buffer, 0, n);
            }
        } finally {
            instream.close();
        }
        String result = writer.toString();
        return result;
    }

    public void download(String format) {
        // force charging urls
        if (info == null)
            getUrls();

        String downloadUrl = info.urls.get(format);
        if (downloadUrl == null) {
            throw new RuntimeException("Available format " + info.urls.keySet());
        }
        downloadWithHttpClient(downloadUrl, getFile(format), info.sizes.get(format));
    }

    private void downloadWithHttpClient(String downloadUrl,
            File outputfile, long expectedSize) {
        try {
            HttpGet httpget = new HttpGet(downloadUrl);
            httpget.setHeader("User-Agent", info.userAgent);
            HttpResponse response = info.httpclient.execute(httpget,
                    info.localContext);
            HttpEntity entity = response.getEntity();
            if (entity != null && response.getStatusLine().getStatusCode() == 200) {
                long length = entity.getContentLength();
                InputStream instream = entity.getContent();
                if (outputfile.exists()) {
                    if (outputfile.length() == expectedSize) {
                        System.out.println("file already exists");
                        return;
                    }
                    else {
                        System.out.println("file is corrupted -> force download");
                        outputfile.delete();
                    }
                } else {
                    DefaultWriter.createDirIfNecessary(outputfile.toURI());
                }                
                FileOutputStream outstream = new FileOutputStream(outputfile);                
                try {
                    byte[] buffer = new byte[2048];
                    int count = -1;
                    long downloadBytes = 0;
                    while ((count = instream.read(buffer)) != -1) {
                        outstream.write(buffer, 0, count);
                        downloadBytes += count;
                        setProgress((int) Math.round(100.0 * downloadBytes / length));
                    }
                    outstream.flush();
                } finally {
                    outstream.close();
                }
            } else {
                throw new RuntimeException("HtmlError:"
                        + response.getStatusLine().getStatusCode() + " for "
                        + downloadUrl);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

}
